package com.zh.filetransport.core

import com.zh.filetransport.indicator.ProgressIndicatorFactory
import com.zh.filetransport.model.FileMetadata
import com.zh.filetransport.model.FileTask
import com.zh.filetransport.model.gson
import com.zh.mycommon.util.MyFileUtil.sizeOfDirectory
import com.zh.mycommon.util.MyFileUtil.sizeOfFile
import com.zh.mycommon.util.MyNetworkUtil.isReachable
import org.tinylog.Logger
import java.io.IOException
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.channels.FileChannel
import java.nio.channels.SocketChannel
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.util.concurrent.LinkedBlockingQueue
import kotlin.concurrent.thread

private val log = Logger.tag("FileSenderKt")

val taskQueue = LinkedBlockingQueue<FileTask>(100)

private lateinit var fileSender: FileSender

fun createFileSender(port: Int, mode: String) {
    fileSender = FileSender(port, mode)
}

fun GetFileSender() = fileSender

// 文件发送器类
class FileSender(private val port: Int, private val mode: String) {
    private var isClosed = true
    private lateinit var socketChannel: SocketChannel
    private var remoteHost: String = ""
    private val progressIndicator = ProgressIndicatorFactory.create(mode)

    private val listeningTh = thread(start = false) { doListeningTasks() };

    fun listeningTask() = listeningTh
    fun remoteHost() = remoteHost

    // 打开连接，其实就是设置RemoteHost值
    fun open(host: String): Boolean {
        if (host.isEmpty()) return false
        val reachable = isReachable(host)
        if (reachable) {
            remoteHost = host
        }
        return reachable
    }

    /**
     * 尝试连接
     */
    fun tryConnect(host: String): Boolean {
        return try {
            val socket = SocketChannel.open()
            socket.configureBlocking(true)
            socket.connect(InetSocketAddress(host, port))
            socket.close()
            true
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }

    // 添加任务
    fun addToTask(fileOrDirPath: String, id: String) {
        if (remoteHost.isEmpty()) {
            log.info("sender.remoteHost 没有值","")
            return
        }
        log.debug("add to task : file = $fileOrDirPath, remote = $remoteHost","")

        try {
            taskQueue.add(
                FileTask(
                    id = id,
                    filePath = fileOrDirPath,
                    remoteIp = remoteHost
                )
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // 监听任务队列
    private fun doListeningTasks() {
        log.info("listeningTasks ...","")

        while (true) {
            try {
                val task = taskQueue.take() //阻塞获取

                // 先创建连接
                if (isClosed) {
                    socketChannel = connectTo(task.remoteIp, port)
                    isClosed = false
                }

                // 执行传输任务
                execTask(task)

                // 如果任务队列为空则关闭连接
                if (taskQueue.isEmpty()) {
                    log.debug("taskQueue is empty. socket channel close ...","")
                    socketChannel.shutdownOutput()
                    isClosed = true
                }
            } catch (e: InterruptedException) {
                break
            } catch (e: Exception) {
                e.printStackTrace()
                break
            }
        }

        log.info("listeningTasks stop ...","")
    }

    // 执行传输任务
    private fun execTask(task: FileTask) {
        try {
            val filePath = task.filePath
            val path = Paths.get(filePath)

            if (!Files.exists(path)) {
                log.warn("新添加的任务文件path不存在: $filePath","")
                return
            }
            progressIndicator.bind(task.id, task.getFileOrDirName()) //绑定任务信息
            progressIndicator.accept(0, task.totalSize)

//            println("正在发送文件 ${task.getFileOrDirName()}")

            if (Files.isDirectory(path)) {
                // 获取目录的size
                task.totalSize = sizeOfDirectory(path.toFile())
                task.isDir = true

                var sentSize: Long = 0

                Files.walk(path).forEach { file ->
                    if (!Files.isDirectory(file)) {
                        val basePath = path.parent.relativize(file.parent).toString()
                        sendFile(basePath, file.toString(), task.totalSize)
                        sentSize += Files.size(file)
                        progressIndicator.accept(sentSize, task.totalSize)
                    }
                }
            } else {
                task.totalSize = Files.size(path)
                sendFile("", filePath, task.totalSize)
            }
            //进度条100%
            progressIndicator.accept(task.totalSize, task.totalSize)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // 发送文件
    private fun sendFile(basePath: String, filePath: String, totalSize: Long) {
        val file = Paths.get(filePath).toFile()
        if (!file.exists()) {
            log.error("文件不存在: {}", filePath)
            return
        }

        //文件元数据
        val metadata = FileMetadata(
            basePath = basePath,
            name = file.name,
            size = sizeOfFile(file),
            totalSize = totalSize
        )
        val jsonBytes = gson.toJson(metadata).toByteArray(StandardCharsets.UTF_8)
//        println("debug >>> metaJson = ${String(jsonBytes)}")

        // 发送元数据长度+元数据
        apply {
            val metaLenBuffer = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN)
            val metaDataBuffer = ByteBuffer.allocate(jsonBytes.size).order(ByteOrder.BIG_ENDIAN)
            //write元数据长度
            metaLenBuffer.putInt(jsonBytes.size)
            metaLenBuffer.flip()
            while (metaLenBuffer.hasRemaining()) {
                socketChannel.write(metaLenBuffer)
            }
            //write元数据内容
            metaDataBuffer.put(jsonBytes)
            metaDataBuffer.flip()
            while (metaDataBuffer.hasRemaining()) {
                socketChannel.write(metaDataBuffer)
            }
        }

        try {
            // 开始传输文件
            FileChannel.open(file.toPath(), StandardOpenOption.READ).use { fileChannel ->
                // 在循环中添加进度更新
                var transferred = 0L
                while (transferred < metadata.size) {
                    val n = fileChannel.transferTo(transferred, metadata.size - transferred, socketChannel)
                    transferred += n

                    // 更新进度指示器（只针对单文件的任务，因为多文件的在外层更新进度）
                    if (!metadata.isDir()) {
                        progressIndicator.accept(transferred, totalSize)
                    }
                }
            }
        } catch (e: Exception) {
            log.error("发送文件失败: {}", e.message)
        }
    }

    // 连接到服务器
    private fun connectTo(host: String, port: Int): SocketChannel {
        try {
            val socketChannel = SocketChannel.open()
            socketChannel.configureBlocking(true)
            socketChannel.connect(InetSocketAddress(host, port))
            log.debug("已连接到 $host:$port","")
            return socketChannel
        } catch (e: IOException) {
            log.warn("连接失败: ${e.message}","")
            throw e
        }
    }

}
